using System;
using System.Reflection;
using System.Collections.Generic;

using log4net;
using Tao.Lua;

namespace Playtime.RegionModule
{
	/// <summary>
	/// Interface definition for all API classes
	/// </summary>
	public interface IModule
	{
		void Register(IntPtr luaState);
	}

	public abstract class Module : IModule
	{
		public string ModuleName;
		
		protected static readonly ILog mLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		// functions are the equivalent to static methods
		protected static Dictionary<string, Lua.lua_CFunction> mFunctions = new Dictionary<string, Lua.lua_CFunction>();
		protected Dictionary<string, Lua.lua_CFunction> Functions { get { return mFunctions; } }
	
		public void AddFunction(string name, Lua.lua_CFunction func)
		{
			mFunctions.Add(name, func);
		}

		public virtual Module Require(Type t)
		{
			throw new NotImplementedException();
		}
		
		/// <summary>
		/// Registers a module in the environment placed on the stack
		/// </summary>
		/// <description>
		/// The algorithm emulates the logic behind the module function, but uses the table at the 
		/// Top of The stack.
		/// </description>
		/// <param name="luaState">
		/// A <see cref="IntPtr"/>
		/// </param>
		public virtual void Register(IntPtr luaState)
		{
			if(mFunctions.Count == 0)
			{
//				mLog.Warn("[" + RegionModule.ModuleName + "]" + " RegisterModule called with nothing to do");
				return;
			}
			
			if(Lua.lua_istable(luaState, -1))
			{
				mLog.Warn("[" + RegionModule.ModuleName + "]" + " RegisterModule must be called with a table on top of the stack");
				return;
			}

			// Register the functions (if any)
			if(mFunctions.Count > 0)
			{
				Lua.lua_newtable(luaState);						// local m = {}
				int m = Lua.lua_gettop(luaState);
				
				Lua.lua_pushstring(luaState, "_G");				// _G[<ModuleName>] = m
				Lua.lua_rawget(luaState, 1);
				int _G = Lua.lua_gettop(luaState);
				Lua.lua_pushstring(luaState, ModuleName);		
				Lua.lua_pushvalue(luaState, m);
				Lua.lua_rawset(luaState, _G);
				Lua.lua_settop(luaState, m);
				
				Lua.lua_pushstring(luaState, "package");		// package.loaded[<ModuleName>] = m
				Lua.lua_rawget(luaState, 1);
				int package = Lua.lua_gettop(luaState);
				Lua.lua_pushstring(luaState, "loaded");
				Lua.lua_rawget(luaState, package);
				int loaded = Lua.lua_gettop(luaState);
				Lua.lua_pushstring(luaState, ModuleName);
				Lua.lua_pushvalue(luaState, m);
				Lua.lua_rawset(luaState, loaded);

				Lua.lua_settop(luaState, m);
				
				Lua.luaL_Reg[] functions = new Lua.luaL_Reg[mFunctions.Count+1];
				int cnt=0;
				foreach(KeyValuePair<string, Lua.lua_CFunction> r in mFunctions)
				{
					functions[cnt].name = r.Key;
					functions[cnt].func = r.Value;
					++cnt;
				}
				functions[cnt].name = null;		// add termination sentinel
				functions[cnt].func = null;

				// register the functions (statics) on the primary table
				Lua.luaL_register(luaState, null, functions);
			}
		}
	}
}

